
package com.jf.cloud.payment.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.write.builder.ExcelWriterBuilder;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.jf.cloud.api.order.constant.FinanceType;
import com.jf.cloud.api.order.feign.OrderRefundFeignClient;
import com.jf.cloud.api.order.vo.AccountOrderDetailVO;
import com.jf.cloud.api.order.vo.PageShopAccountOrderVO;
import com.jf.cloud.api.order.vo.ShopAccountOrderDetailVO;
import com.jf.cloud.api.payment.vo.AccountDetailVO;
import com.jf.cloud.api.payment.vo.ShopAccountDetailVO;
import com.jf.cloud.api.user.feign.UserLevelAndScoreOrderFeignClient;
import com.jf.cloud.common.constant.Constant;
import com.jf.cloud.common.constant.PayType;
import com.jf.cloud.common.database.dto.PageDTO;
import com.jf.cloud.common.database.util.PageUtil;
import com.jf.cloud.common.database.vo.PageVO;
import com.jf.cloud.common.exception.LuckException;
import com.jf.cloud.common.order.bo.PayRefundBO;
import com.jf.cloud.common.order.bo.RefundNotifyBO;
import com.jf.cloud.common.response.ResponseEnum;
import com.jf.cloud.common.response.ServerResponseEntity;
import com.jf.cloud.common.rocketmq.config.RocketMqConstant;
import com.jf.cloud.common.util.Arith;
import com.jf.cloud.common.util.ExcelUtil;
import com.jf.cloud.common.util.PriceUtil;
import com.jf.cloud.payment.bo.RefundInfoBO;
import com.jf.cloud.payment.constant.PayEntry;
import com.jf.cloud.payment.constant.RefundStatus;
import com.jf.cloud.payment.dto.AccountSearchDTO;
import com.jf.cloud.payment.manager.PayManager;
import com.jf.cloud.payment.mapper.PayInfoMapper;
import com.jf.cloud.payment.mapper.RefundInfoMapper;
import com.jf.cloud.payment.model.PayInfo;
import com.jf.cloud.payment.model.RefundInfo;
import com.jf.cloud.payment.service.RefundInfoService;
import com.jf.cloud.payment.vo.RefundInfoExcelVO;
import com.jf.cloud.payment.vo.RefundInfoVO;
import ma.glasnost.orika.MapperFacade;
import org.apache.rocketmq.client.producer.SendStatus;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.servlet.http.HttpServletResponse;
import java.util.*;

/**
 * 退款信息
 *
 * @author zz
 * @date 2021-03-11 14:45:01
 */
@Service
@RefreshScope
public class RefundInfoServiceImpl implements RefundInfoService {

    private static final Logger log = LoggerFactory.getLogger(RefundInfoServiceImpl.class);

    @Autowired
    private RefundInfoMapper refundInfoMapper;
    @Autowired
    private PayInfoMapper payInfoMapper;
    @Autowired
    private PayManager payManager;
    @Value("${application.domainUrl}")
    private String domainUrl;
    @Autowired
    private RocketMQTemplate orderRefundSuccessTemplate;
    @Autowired
    private UserLevelAndScoreOrderFeignClient userLevelAndScoreOrderFeignClient;
    @Autowired
    private OrderRefundFeignClient orderRefundFeignClient;
    @Autowired
    private MapperFacade mapperFacade;

    @Override
    public PageVO<RefundInfo> page(PageDTO pageDTO) {
        return PageUtil.doPage(pageDTO, () -> refundInfoMapper.list());
    }

    @Override
    public RefundInfo listByRefundIdAndWxPay(Long refundId) {
        return refundInfoMapper.listByRefundIdAndWxPay(refundId).get(0);
    }

    @Override
    public void save(RefundInfo refundInfo) {
        refundInfoMapper.save(refundInfo);
    }

    @Override
    public void update(RefundInfo refundInfo) {
        refundInfoMapper.update(refundInfo);
    }

    @Override
    public void deleteById(Long refundId) {
        refundInfoMapper.deleteById(refundId);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void doRefund(PayRefundBO payRefundBO) {
        if (refundInfoMapper.countByRefundId(payRefundBO.getRefundId()) >0) {
            return;
        }
        List<PayInfo> payInfoList = new ArrayList<>();
        // 预售订单可能有两笔
        if(StrUtil.isNotBlank(payRefundBO.getPayIds())){
            String[] payIds = payRefundBO.getPayIds().split(StrUtil.COMMA);
            List<Long> payIdList = new ArrayList<>();
            for (String payId : payIds) {
                try {
                    payIdList.add(Long.valueOf(payId));
                } catch (Exception e) {
                    log.error("支付idLong类型转换失败：{}", e);
                }
            }
            payInfoList = payInfoMapper.getByPayIds(payIdList);
        }else {
            PayInfo payInfo = payInfoMapper.getByPayId(payRefundBO.getPayId());
            payInfoList.add(payInfo);
        }
        if (CollectionUtil.isEmpty(payInfoList)) {
            return;
        }
        boolean refundSuccess = true;
        RefundInfo successRefundInfo = new RefundInfo();
        Long refundAmount = payRefundBO.getRefundAmount();
        int payNumber = 1;
        payInfoList.sort(Comparator.comparing(PayInfo::getPayAmount).reversed());
        for (PayInfo payInfo : payInfoList) {

            RefundInfo refundInfo = new RefundInfo();
            refundInfo.setRefundId(payRefundBO.getRefundId());
            refundInfo.setRefundAmount(Math.min(refundAmount,payInfo.getPayAmount()));
            refundInfo.setOrderId(payRefundBO.getOrderId());
            refundInfo.setPayId(payInfo.getPayId());
            refundInfo.setPayType(payInfo.getPayType());
            refundInfo.setUnSuccessGroupOrder(payRefundBO.getUnSuccessGroupOrder());
            refundInfo.setUserId(payInfo.getUserId());
            // 保存退款信息 refund_id是唯一（定金预售时不唯一）的，所以能确保只进行一次退款
            refundInfoMapper.save(refundInfo);

            RefundInfoBO refundInfoBO = new RefundInfoBO();
            refundInfoBO.setRefundId(refundInfo.getRefundId());
            refundInfoBO.setRefundAmount(refundInfo.getRefundAmount());
            refundInfoBO.setPayType(PayType.instance(payInfo.getPayType()));
            refundInfoBO.setPayId(payInfo.getPayId());
            refundInfoBO.setPayAmount(payInfo.getPayAmount());
            refundInfoBO.setNotifyUrl(domainUrl + "/ua/notice/refund/order/" + payInfo.getPayType());
            refundInfoBO.setUserId(payInfo.getUserId());
            refundInfoBO.setRefundNumber(payNumber++);
            // 支付宝、余额支付，直接退款都不需要回调的,只要有一个是需要回调则等回调
            if(!payManager.doRefund(refundInfoBO)){
                refundSuccess = false;
            }
            if (!Objects.equals(payRefundBO.getOnlyRefund(), 1) && refundSuccess){
                refundInfo.setCallbackContent(refundInfo.getCallbackContent());
                refundInfo.setCallbackTime(new Date());
                refundInfo.setRefundStatus(RefundStatus.REFUNDED.value());
                refundInfoMapper.updateByIdAndPayId(refundInfo);
                successRefundInfo = refundInfo;
            }
            refundAmount = refundAmount - payInfo.getPayAmount();
            if(refundAmount <= 0){
                break;
            }
        }
        // 是否直接进行退款，不需要任何回调
        if (!Objects.equals(payRefundBO.getOnlyRefund(), 1) && refundSuccess) {
            successRefundInfo.setRefundStatus(RefundStatus.UNREFUND.value());
            // 如果都不需要回调，则直接使用第一个
            refundSuccess(successRefundInfo);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void refundSuccess(RefundInfo refundInfo) {
        // 已经退款
        if (Objects.equals(refundInfo.getRefundStatus(), RefundStatus.REFUNDED.value())) {
            return;
        }
        refundInfo.setCallbackContent(refundInfo.getCallbackContent());
        refundInfo.setCallbackTime(new Date());
        refundInfo.setRefundStatus(RefundStatus.REFUNDED.value());
        refundInfoMapper.update(refundInfo);
        // TODO 如果是多条退款，需要判断最后一条才进行回调?
        RefundNotifyBO refundNotifyBO = new RefundNotifyBO();
        refundNotifyBO.setRefundId(refundInfo.getRefundId());
        refundNotifyBO.setRefundAmount(refundInfo.getRefundAmount());
        refundNotifyBO.setOrderId(refundInfo.getOrderId());
        refundNotifyBO.setPayId(refundInfo.getPayId());
        refundNotifyBO.setUnSuccessGroupOrder(refundInfo.getUnSuccessGroupOrder());
        // 通知订单将订单的退款状态改变
        SendStatus sendOrderStatus = orderRefundSuccessTemplate.syncSend(RocketMqConstant.ORDER_REFUND_SUCCESS_TOPIC, new GenericMessage<>(refundNotifyBO)).getSendStatus();


        if (!Objects.equals(sendOrderStatus, SendStatus.SEND_OK)) {
            // 这个回调方法会多次进行回调
            throw new LuckException(ResponseEnum.EXCEPTION);
        }

    }

    @Override
    public AccountDetailVO getRefundAccountDetail(Date startTime, Date endTime, String shopName) {
        ServerResponseEntity<AccountOrderDetailVO> refundResponse = orderRefundFeignClient.getAccountOrderDetail(startTime, endTime, shopName, null);
        if (!refundResponse.isSuccess()) {
            throw new LuckException(refundResponse.getMsg());
        }
        AccountDetailVO accountDetailVO = mapperFacade.map(refundResponse.getData(), AccountDetailVO.class);
        if (Objects.isNull(accountDetailVO)) {
            AccountDetailVO accountDetail = new AccountDetailVO();
            accountDetail.setWechatAmount(0L);
            accountDetail.setAlipayAmount(0L);
            accountDetail.setBalanceAmount(0L);
            accountDetail.setScoreCount(0L);
            accountDetail.setWechatPercent(0.00);
            accountDetail.setAlipayPercent(0.00);
            accountDetail.setBalancePercent(0.00);
            accountDetail.setTotal(0L);
            return accountDetail;
        } else {
            getTotal(accountDetailVO);
            return accountDetailVO;
        }
    }

    @Override
    public AccountDetailVO getPlatformRefundAccountDetail(Date startTime, Date endTime) {
        //todo 获取平台信息,目前购买会员和余额充值都不能退款
//            ShopAccountDetailVO platformAccountInfo = refundInfoMapper.getPlatformAccountInfo(accountSearchDTO.getStartTime(), accountSearchDTO.getEndTime());
        ServerResponseEntity<AccountOrderDetailVO> platformOrderResponse = orderRefundFeignClient.getAccountOrderDetail(startTime, endTime, "", Constant.PLATFORM_SHOP_ID);
        if (!platformOrderResponse.isSuccess()) {
            throw new LuckException(platformOrderResponse.getMsg());
        }
        AccountOrderDetailVO accountOrderDetailVO = platformOrderResponse.getData();
        AccountDetailVO accountDetailVO = new AccountDetailVO();
        accountDetailVO.setAlipayAmount(Objects.isNull(accountOrderDetailVO.getAlipayAmount()) ? 0L : accountOrderDetailVO.getAlipayAmount());
        accountDetailVO.setWechatAmount(Objects.isNull(accountOrderDetailVO.getWechatAmount()) ? 0L : accountOrderDetailVO.getWechatAmount());
        accountDetailVO.setBalanceAmount(Objects.isNull(accountOrderDetailVO.getBalanceAmount()) ? 0L : accountOrderDetailVO.getBalanceAmount());
        accountDetailVO.setScoreCount(Objects.isNull(accountOrderDetailVO.getScoreCount()) ? 0L : accountOrderDetailVO.getScoreCount());
        accountDetailVO.setTotal(accountDetailVO.getAlipayAmount() + accountDetailVO.getBalanceAmount() + accountDetailVO.getWechatAmount());
        return accountDetailVO;
    }

    @Override
    public PageVO<RefundInfoVO> getRefundInfoPage(PageDTO pageDTO, AccountSearchDTO accountSearchDTO) {
        PageVO<RefundInfoVO> pageVO = new PageVO<>();
//        if (Objects.equals(accountSearchDTO.getShopId(), Constant.PLATFORM_SHOP_ID)) {
//            if (Objects.equals(accountSearchDTO.getFinanceType(), FinanceType.RECHARGE.value())) {
//                pageVO =  PageUtil.doPage(pageDTO, () -> refundInfoMapper.listRefundInfoVO(accountSearchDTO));
//            } else if (Objects.equals(accountSearchDTO.getFinanceType(), FinanceType.LEVEL.value())) {
//                pageVO =  PageUtil.doPage(pageDTO, () -> refundInfoMapper.listRefundInfoVO(accountSearchDTO));
//            }
//        }
//        else {
            ServerResponseEntity<PageShopAccountOrderVO> orderResponse = orderRefundFeignClient.listOrderRefundInfo(accountSearchDTO.getStartTime(), accountSearchDTO.getEndTime(), accountSearchDTO.getShopId(),null, pageDTO.getPageSize(), pageDTO.getPageNum());
            if (!orderResponse.isSuccess()) {
                throw new LuckException(orderResponse.getMsg());
            }
            pageVO.setTotal(orderResponse.getData().getTotal());
            pageVO.setList(mapperFacade.mapAsList(orderResponse.getData().getRefundInfoList(), RefundInfoVO.class));
            pageVO.setPages(PageUtil.getPages(orderResponse.getData().getTotal(), pageDTO.getPageSize()));
//        }
        return pageVO;
    }

    @Override
    public void excelRefundInfo(HttpServletResponse response, AccountSearchDTO accountSearchDTO) {
        accountSearchDTO.setFinanceType(FinanceType.ORDER.value());
        ServerResponseEntity<PageShopAccountOrderVO> orderRefundResponse = orderRefundFeignClient.listOrderRefundInfo(accountSearchDTO.getStartTime(), accountSearchDTO.getEndTime(), null, accountSearchDTO.getShopName(), null, null);
        if (!orderRefundResponse.isSuccess()) {
            throw new LuckException(orderRefundResponse.getMsg());
        }
        List<RefundInfoVO> orderRefundInfoList = mapperFacade.mapAsList(orderRefundResponse.getData().getRefundInfoList(), RefundInfoVO.class);
//        accountSearchDTO.setFinanceType(FinanceType.RECHARGE.value());
//        List<RefundInfoVO> rechargePayInfoList = refundInfoMapper.listRefundInfoVO(accountSearchDTO);
//        accountSearchDTO.setFinanceType(FinanceType.LEVEL.value());
//        List<RefundInfoVO> levelPayInfoList = refundInfoMapper.listRefundInfoVO(accountSearchDTO);

        Map<Integer, String[]> map = new HashMap<>(8);
        ExcelWriter excelWriter = null;
        try{
            // 先执行合并策略
            ExcelWriterBuilder excelWriterMerge = ExcelUtil.getExcelWriterMerge(response, RefundInfoExcelVO.EXCEL_NAME, RefundInfoExcelVO.MERGE_ROW_INDEX, RefundInfoExcelVO.MERGE_COLUMN_INDEX);
            excelWriter = ExcelUtil.getExcelModel(excelWriterMerge, map, Constant.START_ROW).build();
            // 业务代码
            writeExcel(orderRefundInfoList, FinanceType.ORDER.str(), excelWriter);
//            writeExcel(rechargePayInfoList, FinanceType.RECHARGE.str(), excelWriter);
//            writeExcel(levelPayInfoList, FinanceType.LEVEL.str(), excelWriter);
        } catch (Exception e) {
            log.error("导出财务对账退款报表出错", e);
        } finally {
            if (excelWriter != null) {
                excelWriter.finish();
            }
        }
    }

    private List<RefundInfoExcelVO> getExcelList(List<RefundInfoVO> refundInfoList) {
        List<RefundInfoExcelVO> refundInfoExcelList = new ArrayList<>();
        int index = 1;
        for (RefundInfoVO refundInfoVO : refundInfoList) {
            RefundInfoExcelVO refundInfoExcelVO = new RefundInfoExcelVO();
            refundInfoExcelVO.setSeq(String.valueOf(index));
            refundInfoExcelVO.setShopName(refundInfoVO.getShopName());
            refundInfoExcelVO.setRefundId(refundInfoVO.getRefundId().toString());
            refundInfoExcelVO.setPayId(refundInfoVO.getPayId().toString());
            refundInfoExcelVO.setOrderId(refundInfoVO.getOrderId().toString());
            refundInfoExcelVO.setPayEntry(PayEntry.getPayTypeName(refundInfoVO.getPayEntry()));
            refundInfoExcelVO.setPayType(PayType.getPayTypeName(refundInfoVO.getPayType()));
            refundInfoExcelVO.setPayScore(Objects.isNull(refundInfoVO.getScoreCount()) ? "0" : refundInfoVO.getScoreCount().toString());
            refundInfoExcelVO.setAlipayAmount(PriceUtil.toDecimalPrice(refundInfoVO.getAlipayAmount()).toString());
            refundInfoExcelVO.setWechatAmount(PriceUtil.toDecimalPrice(refundInfoVO.getWechatAmount()).toString());
            refundInfoExcelVO.setBalanceAmount(PriceUtil.toDecimalPrice(refundInfoVO.getBalanceAmount()).toString());
            refundInfoExcelVO.setRefundTime(refundInfoVO.getRefundTime());
            refundInfoExcelList.add(refundInfoExcelVO);
            index++;
        }
        return refundInfoExcelList;
    }

    private void writeExcel(List<RefundInfoVO> refundInfoList, String type, ExcelWriter excelWriter) {
        List<RefundInfoExcelVO> refundInfoExcelList = getExcelList(refundInfoList);
        if (CollUtil.isNotEmpty(refundInfoExcelList)) {
            refundInfoExcelList.add(new RefundInfoExcelVO());
        }
        WriteSheet sheetWriter = EasyExcel.writerSheet(type).head(RefundInfoExcelVO.class).build();
        excelWriter.write(refundInfoExcelList,sheetWriter);
    }

    @Override
    public PageVO<ShopAccountDetailVO> pageRefundAccountDetail(PageDTO pageDTO, AccountSearchDTO accountSearchDTO) {
        // 获取店铺信息
        ServerResponseEntity<PageShopAccountOrderVO> orderResponse = orderRefundFeignClient.listShopAccountDetail(accountSearchDTO.getStartTime(), accountSearchDTO.getEndTime(), accountSearchDTO.getShopName(), pageDTO.getPageSize(), pageDTO.getPageNum());
        if (!orderResponse.isSuccess()) {
            throw new LuckException(orderResponse.getMsg());
        }
        List<ShopAccountOrderDetailVO> shopList = orderResponse.getData().getShopList();
        List<ShopAccountDetailVO> result = mapperFacade.mapAsList(shopList, ShopAccountDetailVO.class);

        PageVO<ShopAccountDetailVO> shopPage = new PageVO<>();
        shopPage.setTotal(orderResponse.getData().getTotal());
        shopPage.setList(result);
        shopPage.setPages(PageUtil.getPages(orderResponse.getData().getTotal(), pageDTO.getPageSize()));
        return shopPage;
    }


    @Override
    public void updateByIdPayId(RefundInfo refundInfo) {
        refundInfoMapper.updateByIdAndPayId(refundInfo);
    }

    private void getTotal(AccountDetailVO accountDetailVO) {
        Long wechatAmount = Objects.isNull(accountDetailVO.getWechatAmount()) ? 0L : accountDetailVO.getWechatAmount();
        Long alipayAmount = Objects.isNull(accountDetailVO.getAlipayAmount()) ? 0L : accountDetailVO.getAlipayAmount();
        Long balanceAmount = Objects.isNull(accountDetailVO.getBalanceAmount()) ? 0L : accountDetailVO.getBalanceAmount();
        long total = wechatAmount + alipayAmount + balanceAmount;
        accountDetailVO.setTotal(total);
        accountDetailVO.setWechatPercent(Arith.div(wechatAmount.doubleValue(), (double) total, 4));
        accountDetailVO.setAlipayPercent(Arith.div(alipayAmount.doubleValue(), (double) total, 4));
        accountDetailVO.setBalancePercent(Arith.div(balanceAmount.doubleValue(), (double) total, 4));
    }
}
